package cn.iocoder.yudao.module.crm.service.clue;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.poi.excel.BigExcelWriter;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.call.api.record.CallRecordApi;
import cn.iocoder.yudao.module.call.api.record.dto.CallRecordDTO;
import cn.iocoder.yudao.module.crm.api.follow.CrmFollowRecordApi;
import cn.iocoder.yudao.module.crm.api.location.NumberLocationApi;
import cn.iocoder.yudao.module.crm.api.location.dto.NumberLocationDTO;
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.*;
import cn.iocoder.yudao.module.crm.controller.admin.customFieldHead.vo.CustomFieldHeadPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.CrmCustomerDistributeReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.CrmCustomerSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.importtask.vo.ImportTaskSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueLabelDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmCluePoolConfigDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customFieldHead.CustomFieldHeadDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customfieldData.CustomFieldDataDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.label.CrmLabelDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.levelconfig.CrmLevelConfigDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.module.CrmModuleDO;
import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueLabelMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.customfieldData.CustomFieldDataMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.label.CrmLabelMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.levelconfig.CrmLevelConfigMapper;
import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
import cn.iocoder.yudao.module.crm.enums.common.*;
import cn.iocoder.yudao.module.crm.enums.customField.FieldEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.customFieldHead.CustomFieldHeadService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO;
import cn.iocoder.yudao.module.crm.service.customfieldData.CustomFieldDataService;
import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService;
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmFollowUpCreateReqBO;
import cn.iocoder.yudao.module.crm.service.importtask.ImportTaskService;
import cn.iocoder.yudao.module.crm.service.module.CrmModuleService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.crm.service.pool.CrmPoolActionLogService;
import cn.iocoder.yudao.module.crm.util.BaseUtil;
import cn.iocoder.yudao.module.crm.util.CrmExcelUtil;
import cn.iocoder.yudao.module.crm.util.ExcelParseUtil;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.io.BufferedInputStream;
import java.io.File;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;

/**
 * 线索 Service 实现类
 *
 * @author Wanwan
 */
@Service
@Validated
@Slf4j
public class CrmClueServiceImpl implements CrmClueService {

    @Resource
    private CrmClueMapper clueMapper;
    @Resource
    private CustomFieldDataMapper customFieldDataMapper;
    @Resource
    private CustomFieldDataService customFieldDataService;


    @Resource
    private CrmCustomerService customerService;
    @Resource
    private CrmPermissionService crmPermissionService;

    @Resource
    private CrmCluePoolConfigService cluePoolConfigService;
    @Resource
    private CrmFollowUpRecordService followUpRecordService;

    @Resource
    private AdminUserApi adminUserApi;

    @Resource
    private CrmClueLabelMapper crmClueLabelMapper;

    @Resource
    private CrmLabelMapper crmLabelMapper;

    @Resource
    private CrmLevelConfigMapper crmLevelConfigMapper;


    @Resource
    private CrmModuleService moduleService;

    @Resource
    private CrmPoolActionLogService crmPoolActionLogService;

    @Resource
    private CallRecordApi callRecordApi;

    @Resource
    private CrmFollowRecordApi crmFollowRecordApi;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private CustomFieldHeadService customFieldHeadService;

    @Resource
    private FileApi fileApi;

    private String uploadPath = "D:/upload/private";

    @Resource
    private DictDataApi dictDataApi;
    @Resource
    private NumberLocationApi locationApi;

    @Override
    public List<Long> getIdByNumber(String number) {
        List<CrmClueDO> clueDOList = clueMapper.selectList(new LambdaQueryWrapper<CrmClueDO>()
                .select(CrmClueDO::getId)
                .eq(CrmClueDO::getMobile, number)
                .eq(CrmClueDO::getOwnerUserId, getLoginUserId())
        );
        List<CrmClueDO> clueDOList2 = clueMapper.selectList(new LambdaQueryWrapper<CrmClueDO>()
                .select(CrmClueDO::getId)
                .eq(CrmClueDO::getTelephone, number)
                .eq(CrmClueDO::getOwnerUserId, getLoginUserId())
        );
        clueDOList.addAll(clueDOList2);
        //去重一下
        return new ArrayList<>(new HashSet<>(CollectionUtils.convertList(clueDOList, CrmClueDO::getId)));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_CREATE_SUB_TYPE, bizNo = "{{#clue.id}}",
            success = CRM_CLUE_CREATE_SUCCESS)
    public Long createClue(CrmClueSaveReqVO createReqVO) {
        // 1.1 校验关联数据
        validateRelationDataExists(createReqVO);
        // 1.2 校验负责人是否存在
        adminUserApi.validateUser(createReqVO.getOwnerUserId());

        // 2. 插入线索
        CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class);
        clue.setOwnerTime(LocalDateTime.now());
        clue.setDataType(CrmDataTypeEnum.OWNER.getType());
        // 添加level等级
        if (Objects.nonNull(createReqVO.getLevelId())) {
            CrmLevelConfigDO level = crmLevelConfigMapper.selectById(createReqVO.getLevelId());
            if (Objects.nonNull(level)) {
                clue.setLevelId(level.getId());
                clue.setLevelValue(level.getSort());
            }
        }
        appendLocation(clue);
        clueMapper.insert(clue);
        if (CollUtil.isNotEmpty(createReqVO.getCustomFieldList())) {
            List<CustomFieldDataDO> customFieldDataList = createReqVO.getCustomFieldList();
            for (CustomFieldDataDO customFieldDataDO : customFieldDataList) {
                customFieldDataDO.setDataId(clue.getId());
            }
            customFieldDataMapper.insertBatch(customFieldDataList);
        }

        // 添加标签
        if (CollectionUtil.isNotEmpty(createReqVO.getLabelList())) {
            List<Long> labelIds = createReqVO.getLabelList();
            labelIds.forEach(labelId -> {
                crmClueLabelMapper.insert(new CrmClueLabelDO().setClueId(clue.getId()).setLabelId(labelId));
            });
        }

        // 3. 创建数据权限
        CrmPermissionCreateReqBO createReqBO = new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CLUE.getType())
                .setBizId(clue.getId()).setUserId(clue.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel());
        crmPermissionService.createPermission(createReqBO);

        // 5. 检查是否携带了reqNo, 然后去请求reqNo
        if (StrUtil.isNotEmpty(createReqVO.getReqNo())) {
            // 获取话单对象
            CallRecordDTO recordByReqNo = callRecordApi.getCallRecordByReqNo(createReqVO.getReqNo());
            if (Objects.nonNull(recordByReqNo)) {
                if (Objects.equals(0, recordByReqNo.getIsClue())) {
                    // 话单没有生成线索时, 创建跟进记录
                    crmFollowRecordApi.addCrmCallFollowRecord(
                            CrmBizTypeEnum.CRM_CLUE,
                            clue.getOwnerUserId(),
                            clue.getId(),
                            recordByReqNo.getId(),
                            recordByReqNo.getResultType() == 1,
                            recordByReqNo.getRecordUrl(),
                            recordByReqNo.getPhoneName()
                    );

                    // 设置最后一次callId
                    clueMapper.updateById(new CrmClueDO().setId(clue.getId()).setLastCallId(recordByReqNo.getId()));
                } else {
                    // 已经生成线索时, 把旧的线索删除
                    // 这里有个情况,  如果话单已经生成, 并且已经转入了线索, 此时用户又操作了一次转线索, 那么就会产生两个线索, 所以必须在这里的查询基础上
                    // 把话单自动生成的线索删除, 然后把用户手动转入的线索创建出来;  然后修改话单绑定的线索id(新的);
                    clueMapper.deleteById(recordByReqNo.getClueId());
                    callRecordApi.updateCallRecordClueInfo(recordByReqNo.getId(), clue.getId());
                }
            } else {
                // 正在通话中时, 操作了转线索, 所以这里创建了一个临时缓存, 用来等待话单回调进入从而去执行 -> 写跟进(关联线索对象);
                String cacheBody = stringRedisTemplate.opsForValue().get("call:req_no:" + createReqVO.getReqNo());
                if (StrUtil.isNotEmpty(cacheBody)) {
                    JSONObject jsonObject = JSONUtil.parseObj(cacheBody);
                    jsonObject.set("source", "clue");
                    jsonObject.set("sourceId", clue.getId());
                    stringRedisTemplate.opsForValue().set("call:req_no:" + createReqVO.getReqNo(), JSONUtil.toJsonStr(jsonObject));
                }

//                Map<String, Object> cacheData = new HashMap<>();
//                cacheData.put("source", "clue");
//                cacheData.put("sourceId", clue.getId());
//                cacheData.put("ownerUserId", getLoginUserId());
//                cacheData.put("tenantId", TenantContextHolder.getTenantId());
//                stringRedisTemplate.opsForValue().set("call_req_no_map:" + createReqVO.getReqNo(), JSONUtil.toJsonStr(cacheData), 24, TimeUnit.HOURS );
            }
        }

        // 4. 记录操作日志上下文
        LogRecordContext.putVariable("clue", clue);
        return clue.getId();
    }


    @Override
    public Long createClueWithoutUser(CrmClueSaveReqVO createReqVO) {
        // 2. 插入线索
        CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class);
        clue.setOwnerTime(LocalDateTime.now());
        appendLocation(clue);
        clueMapper.insert(clue);
        if (!Objects.isNull(clue.getOwnerUserId())) {
            // 3. 创建数据权限
            CrmPermissionCreateReqBO createReqBO = new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CLUE.getType())
                    .setBizId(clue.getId()).setUserId(clue.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel());
            crmPermissionService.createPermission(createReqBO);
        }
        return clue.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_UPDATE_SUB_TYPE, bizNo = "{{#updateReq.id}}",
            success = CRM_CLUE_UPDATE_SUCCESS)
    @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#updateReq.id", level = CrmPermissionLevelEnum.OWNER)
    public void updateClue(CrmClueSaveReqVO updateReq) {
        Assert.notNull(updateReq.getId(), "线索编号不能为空");
        // 1.1 校验线索是否存在
        CrmClueDO oldClue = validateClueExists(updateReq.getId());
        // 1.2 校验关联数据
        validateRelationDataExists(updateReq);

        // 2. 更新线索
        CrmClueDO updateObj = BeanUtils.toBean(updateReq, CrmClueDO.class);

        // 更新线索级别
        if (Objects.isNull(updateReq.getLevelId())) {
            updateObj.setLevelId(null);
            updateObj.setLevelValue(0);
        } else {
            CrmLevelConfigDO level = crmLevelConfigMapper.selectById(updateReq.getLevelId());
            if (Objects.nonNull(level)) {
                updateObj.setLevelId(level.getId());
                updateObj.setLevelValue(level.getSort());
            }
        }
        appendLocation(updateObj);
        clueMapper.updateById(updateObj);
        if (CollUtil.isNotEmpty(updateReq.getCustomFieldList())) {
            customFieldDataMapper.delete(new LambdaQueryWrapperX<CustomFieldDataDO>().eq(CustomFieldDataDO::getDataId, updateObj.getId()));
            List<CustomFieldDataDO> customFieldDataList = updateReq.getCustomFieldList();
            for (CustomFieldDataDO customFieldDataDO : customFieldDataList) {
                customFieldDataDO.setDataId(Long.valueOf(updateObj.getId()));
            }
            customFieldDataMapper.insertBatch(customFieldDataList);
        } else {
            customFieldDataMapper.delete(new LambdaQueryWrapperX<CustomFieldDataDO>().eq(CustomFieldDataDO::getDataId, updateObj.getId()));
        }
        // 3. 记录操作日志上下文
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmCustomerSaveReqVO.class));
        LogRecordContext.putVariable("clueName", oldClue.getName());
    }

    @Override
    public void simpleUpdateClue(CrmClueDO clueDO) {
        appendLocation(clueDO);
        clueMapper.updateById(clueDO);
    }

    private void validateRelationDataExists(CrmClueSaveReqVO reqVO) {
        // 校验负责人
        if (Objects.nonNull(reqVO.getOwnerUserId()) &&
                Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()))) {
            throw exception(USER_NOT_EXISTS);
        }
    }

    @Override
    @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}}",
            success = CRM_CLUE_FOLLOW_UP_SUCCESS)
    @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
    public void updateClueFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) {
        // 校验线索是否存在
        CrmClueDO oldClue = validateClueExists(id);

        // 更新线索
        clueMapper.updateById(new CrmClueDO().setId(id).setFollowUpStatus(true).setContactNextTime(contactNextTime)
                .setContactLastTime(LocalDateTime.now()).setContactLastContent(contactLastContent));

        // 3. 记录操作日志上下文
        LogRecordContext.putVariable("clueName", oldClue.getName());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_DELETE_SUB_TYPE, bizNo = "{{#id}}",
            success = CRM_CLUE_DELETE_SUCCESS)
    @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
    public void deleteClue(Long id) {
        // 1. 校验存在
        CrmClueDO clue = validateClueExists(id);

        // 2. 删除
        clueMapper.deleteById(id);

        // 3. 删除数据权限
        crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CLUE.getType(), id);

        // 4. 删除跟进
        followUpRecordService.deleteFollowUpRecordByBiz(CrmBizTypeEnum.CRM_CLUE.getType(), id);
        customFieldDataMapper.delete(new LambdaQueryWrapperX<CustomFieldDataDO>().eq(CustomFieldDataDO::getDataId, id));
        // 5. 记录操作日志上下文
        LogRecordContext.putVariable("clueName", clue.getName());
    }

    @Override
    public void batchDeleteClue(List<Long> ids) {
        ids.forEach(this::deleteClue);
    }

    @Override
    public void batchDeleteCluePool(List<Long> ids) {
        ids.forEach(this::deleteCluePool);
    }

    @Override
    public void deleteCluePool(Long id) {
        // 1. 校验存在
        CrmClueDO clue = validateClueExists(id);

        // 2. 删除
        clueMapper.deleteById(id);

        // 4. 删除跟进
        followUpRecordService.deleteFollowUpRecordByBiz(CrmBizTypeEnum.CRM_CLUE.getType(), id);

        // 5. 记录操作日志上下文
        LogRecordContext.putVariable("clueName", clue.getName());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}",
            success = CRM_CLUE_TRANSFER_SUCCESS)
    @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
    public void transferClue(CrmClueTransferReqVO reqVO, Long userId) {
        // 1 校验线索是否存在
        CrmClueDO clue = validateClueExists(reqVO.getId());

        // 2.1 数据权限转移
        crmPermissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CLUE.getType(),
                reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
        // 2.2 设置新的负责人
        clueMapper.updateById(new CrmClueDO()
                .setId(reqVO.getId())
                .setOwnerUserId(reqVO.getNewOwnerUserId())
                .setOwnerTime(LocalDateTime.now())
                // 更新这个线索的数据类型为，转移给我
                .setDataType(CrmDataTypeEnum.TRANSFER.getType())
        );

        // 3. 记录转移日志
        LogRecordContext.putVariable("clue", clue);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_TRANSLATE_SUB_TYPE, bizNo = "{{#id}}",
            success = CRM_CLUE_TRANSLATE_SUCCESS)
    @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
    public void transformClue(Long id, Long userId) {
        // 1.1 校验线索都存在
        CrmClueDO clue = validateClueExists(id);
        // 1.2 存在已经转化的
        if (clue.getTransformStatus()) {
            throw exception(CLUE_TRANSFORM_FAIL_ALREADY);
        }

        // 获取线索的标签，然后复制给客户
        List<CrmClueLabelDO> selectList = crmClueLabelMapper.selectList(CrmClueLabelDO::getClueId, id);
        CrmCustomerCreateReqBO toSaveBean = BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class);
        if (CollUtil.isNotEmpty(selectList)) {
            toSaveBean.setLabelList(selectList.stream().map(CrmClueLabelDO::getLabelId).collect(Collectors.toList()));
        }

        // 2.1 遍历线索(未转化的线索)，创建对应的客户
        Long customerId = customerService.createCustomerFromClue(toSaveBean, userId);
        LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<CustomFieldDataDO>()
                .eq(CustomFieldDataDO::getDataId, id)
                .set(CustomFieldDataDO::getDataId, customerId);
        customFieldDataMapper.update(updateWrapper);

        // 2.2 更新线索
        clueMapper.updateById(new CrmClueDO().setId(id).setTransformStatus(Boolean.TRUE).setCustomerId(customerId));

        // 2.3 复制跟进记录
        List<CrmFollowUpRecordDO> followUpRecords = followUpRecordService.getFollowUpRecordByBiz(
                CrmBizTypeEnum.CRM_CLUE.getType(), singleton(clue.getId()));
        if (CollUtil.isNotEmpty(followUpRecords)) {
            followUpRecordService.createFollowUpRecordBatch(convertList(followUpRecords, record ->
                    BeanUtils.toBean(record, CrmFollowUpCreateReqBO.class)
                            .setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()).setBizId(customerId)));
        }

        // 2.4 创建客户的数据和操作权限
//        permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
//                reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
        //标识删除线索
        clueMapper.deleteById(id);
        // 3. 记录操作日志上下文
        LogRecordContext.putVariable("clueName", clue.getName());
    }

    private CrmClueDO validateClueExists(Long id) {
        CrmClueDO crmClueDO = clueMapper.selectById(id);
        if (crmClueDO == null) {
            throw exception(CLUE_NOT_EXISTS);
        }
        return crmClueDO;
    }

    @Override
//    @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.READ)
    public CrmClueDO getClue(Long id) {
        return clueMapper.selectById(id);
    }

    @Override
    public List<CrmClueDO> getClueList(Collection<Long> ids, Long userId) {
        if (CollUtil.isEmpty(ids)) {
            return ListUtil.empty();
        }
        return clueMapper.selectBatchIds(ids, userId);
    }

    @Override
    public PageResult<CrmClueDO> getCluePage(CrmCluePageReqVO pageReqVO, Long userId) {
        return clueMapper.selectPage(pageReqVO, userId);
    }

    @Override
    public PageResult<CrmClueDO> getClueAdvancePage(PageParam pageReqVO, Long userId) {
        return clueMapper.selectAdvancePage(pageReqVO, userId);
    }

    public PageResult<CrmClueDO> getCustomClueAdvancePage(PageParam pageReqVO, Long userId) {
        return clueMapper.selectCustomAdvancePage(pageReqVO, userId);
    }


    @Override
    public PageResult<CrmClueDO> getCluePageByRobot(CrmCluePageReqVO pageReqVO) {
        return clueMapper.selectPage(pageReqVO);
    }

    @Override
    public Long getFollowClueCount(Long userId) {
        return clueMapper.selectCountByFollow(userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_POOL_SUB_TYPE, bizNo = "{{#id}}",
            success = CRM_CLUE_POOL_SUCCESS)
    @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
    public void putCluePool(Long id) {
        CrmClueDO clue = clueMapper.selectById(id);
        if (clue == null) {
            throw exception(CLUE_NOT_EXISTS);
        }
        // 校验pool是否存在
//        CluePoolDO pool = poolMapper.selectById(poolId);
//        if (pool == null) {
//            throw exception(CLUE_POOL_NOT_EXISTS);
//        }

        // 1.2. 校验是否为公海数据
        validateClueOwnerExists(clue, true);

//        clue.setPoolId(poolId);
//        clue.setIntoPoolTime(LocalDateTime.now());
        // 2. 客户放入公海
        putCluePool(clue);

        // 记录操作日志上下文
        LogRecordContext.putVariable("clueName", clue.getName());
    }

    private void validateClueOwnerExists(CrmClueDO clue, Boolean pool) {
        if (clue == null) { // 防御一下
            throw exception(CLUE_NOT_EXISTS);
        }
        // 校验是否为公海数据
        if (pool && clue.getOwnerUserId() == null) {
            throw exception(CLUE_IN_POOL, clue.getName());
        }
//        if (pool && clue.getPoolId() != null) {
//            throw exception(CLUE_IN_POOL, clue.getName());
//        }
        // 负责人已存在
        if (!pool && clue.getOwnerUserId() != null) {
            throw exception(CLUE_HAS_OWNER, clue.getName());
        }

    }

    @Transactional(rollbackFor = Exception.class) // 需要 protected 修饰，因为需要在事务中调用
    public void putCluePool(CrmClueDO clue) {
        // 1. 设置负责人为 NULL
        int updateOwnerUserIncr = clueMapper.updateOwnerUserIdById(clue.getId(), null, clue.getPoolId());
        if (updateOwnerUserIncr == 0) {
            throw exception(CLUE_UPDATE_OWNER_USER_FAIL);
        }

        // 3. 删除负责人数据权限
        // 注意：需要放在 contactService 后面，不然【客户】数据权限已经被删除，无法操作！
        crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CLUE.getType(), clue.getId(),
                CrmPermissionLevelEnum.OWNER.getLevel());

        // 添加公海操作日志
        crmPoolActionLogService.createActionLog(CrmBizTypeEnum.CRM_CLUE.getType(), clue.getId(), CrmPoolActionTypeEnum.PUT.getType());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void receiveClue(List<Long> ids, List<Long> ownerUserIdList, Boolean isReceive) {
        // 1.1 校验存在
        List<CrmClueDO> clues = clueMapper.selectBatchIds(ids);
        if (clues.size() != ids.size()) {
            throw exception(CLUE_NOT_EXISTS);
        }
        // 1.2 校验负责人是否存在
        adminUserApi.validateUserList(ownerUserIdList);
        // 1.3 校验状态
        clues.forEach(customer -> {
            // 校验是否已有负责人
            validateClueOwnerExists(customer, false);
        });
        // 顺序分配
        int index = 0;

        // 2. 领取公海数据
        List<CrmClueDO> updateClues = new ArrayList<>();
        List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>();

        for (CrmClueDO clue : clues) {
            if (index == ownerUserIdList.size()) {
                index = 0;
            }
            try {
//                CrmCustomerDO customer = crmCustomerDO;
                Long ownerUserId = ownerUserIdList.get(index);
                // 2.1. 设置负责人
                updateClues.add(new CrmClueDO().setId(clue.getId())
                        .setOwnerUserId(ownerUserId)
                        .setOwnerTime(LocalDateTime.now())
                        .setPoolId(null)
                        .setIntoPoolTime(null)
                        // 设置数据类型是公海领取还是分配
                        .setDataType(isReceive ? CrmDataTypeEnum.RECEIVE.getType() : CrmDataTypeEnum.DISTRIBUTE.getType())
                );
                // 2.2. 创建负责人数据权限
                createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CLUE.getType())
                        .setBizId(clue.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
                // 3. 记录操作日志
                AdminUserRespDTO user = null;
                if (!isReceive) {
                    user = adminUserApi.getUser(ownerUserId);
                }
                getSelf().receiveCustomerLog(clue, user == null ? null : user.getNickname());

                // 添加公海操作日志
                crmPoolActionLogService.createActionLog(CrmBizTypeEnum.CRM_CLUE.getType(), clue.getId(), CrmPoolActionTypeEnum.GET.getType());
            } catch (Exception e) {
                // continue
            } finally {
                index++;
            }
        }

        // 4.1 更新客户负责人
        clueMapper.updateBatch(updateClues);
        // 4.2 创建负责人数据权限
        crmPermissionService.createPermissionBatch(createPermissions);

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void receiveClue(CrmCustomerDistributeReqVO distributeReqVO, List<Long> ownerUserIdList, Boolean isReceive) {

        if (distributeReqVO.getSize() == null || distributeReqVO.getSize() <= 0) {
            throw new ServiceException(new ErrorCode(501, "暂无数据分配"));
        }
        // 1.1 校验存在
        LambdaQueryWrapper<CrmClueDO> queryWrapper = new LambdaQueryWrapper();
        if (distributeReqVO.getOrderType().equals(1)) {
            queryWrapper.orderByAsc(CrmClueDO::getId);
        } else if (distributeReqVO.getOrderType().equals(2)) {
            queryWrapper.orderByDesc(CrmClueDO::getId);
        }
        if (null != distributeReqVO.getModuleId()) {
            queryWrapper.eq(CrmClueDO::getModuleId, distributeReqVO.getModuleId());
        }
        queryWrapper.and(
                wrapper -> wrapper.isNull(CrmClueDO::getOwnerUserId)
                        .or().eq(CrmClueDO::getOwnerUserId, 0)
        );

        queryWrapper.last("limit " + distributeReqVO.getSize());
        List<CrmClueDO> clues = clueMapper.selectList(queryWrapper);
        // 1.2 校验负责人是否存在
        adminUserApi.validateUserList(ownerUserIdList);
        // 1.3 校验状态
        clues.forEach(customer -> {
            // 校验是否已有负责人
            validateClueOwnerExists(customer, false);
        });
        // 顺序分配
        int index = 0;

        // 2. 领取公海数据
        List<CrmClueDO> updateClues = new ArrayList<>();
        List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>();

        for (CrmClueDO clue : clues) {
            if (index == ownerUserIdList.size()) {
                index = 0;
            }
            try {
//                CrmCustomerDO customer = crmCustomerDO;
                Long ownerUserId = ownerUserIdList.get(index);
                // 2.1. 设置负责人
                updateClues.add(new CrmClueDO().setId(clue.getId())
                        .setOwnerUserId(ownerUserId)
                        .setOwnerTime(LocalDateTime.now())
                        .setPoolId(null)
                        .setIntoPoolTime(null)
                        // 设置数据类型是公海领取还是分配
                        .setDataType(isReceive ? CrmDataTypeEnum.RECEIVE.getType() : CrmDataTypeEnum.DISTRIBUTE.getType())
                );
                // 2.2. 创建负责人数据权限
                createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CLUE.getType())
                        .setBizId(clue.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
                // 3. 记录操作日志
                AdminUserRespDTO user = null;
                if (!isReceive) {
                    user = adminUserApi.getUser(ownerUserId);
                }
                getSelf().receiveCustomerLog(clue, user == null ? null : user.getNickname());

                // 添加公海操作日志
                crmPoolActionLogService.createActionLog(CrmBizTypeEnum.CRM_CLUE.getType(), clue.getId(), CrmPoolActionTypeEnum.GET.getType());
            } catch (Exception e) {
                // continue
            } finally {
                index++;
            }
        }

        // 4.1 更新客户负责人
        clueMapper.updateBatch(updateClues);
        // 4.2 创建负责人数据权限
        crmPermissionService.createPermissionBatch(createPermissions);

    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void receiveClueMq(CrmCustomerDistributeReqVO distributeReqVO, List<Long> ownerUserIdList, Boolean isReceive) {
        if (distributeReqVO.getSize() == null || distributeReqVO.getSize() <= 0) {
            throw new ServiceException(new ErrorCode(501, "暂无数据分配"));
        }
        int pageSize = 100; // 每页大小
        int totalProcessed = 0; // 已处理的数量
        int pageNum = 1; // 当前页码
        while (totalProcessed < distributeReqVO.getSize()) {
            System.out.println("totalProcessed:" + totalProcessed);
            int offset = (pageNum - 1) * pageSize;
            int remaining = distributeReqVO.getSize() - totalProcessed;
            int currentBatchSize = Math.min(pageSize, remaining);
            // 1.1 校验存在
            LambdaQueryWrapper<CrmClueDO> queryWrapper = new LambdaQueryWrapper();
            if (distributeReqVO.getOrderType().equals(1)) {
                queryWrapper.orderByAsc(CrmClueDO::getId);
            } else if (distributeReqVO.getOrderType().equals(2)) {
                queryWrapper.orderByDesc(CrmClueDO::getId);
            }
            if (null != distributeReqVO.getModuleId()) {
                queryWrapper.eq(CrmClueDO::getModuleId, distributeReqVO.getModuleId());
            }
            queryWrapper.and(
                    wrapper -> wrapper.isNull(CrmClueDO::getOwnerUserId)
                            .or().eq(CrmClueDO::getOwnerUserId, 0)
            );

            queryWrapper.last("LIMIT " + currentBatchSize + " OFFSET " + offset);
            List<CrmClueDO> clues = clueMapper.selectList(queryWrapper);
            // 1.2 校验负责人是否存在
            adminUserApi.validateUserList(ownerUserIdList);
            // 1.3 校验状态
            clues.forEach(customer -> {
                // 校验是否已有负责人
                validateClueOwnerExists(customer, false);
            });
            // 顺序分配
            int index = 0;

            // 2. 领取公海数据
            List<CrmClueDO> updateClues = new ArrayList<>();
            List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>();

            for (CrmClueDO clue : clues) {
                if (index == ownerUserIdList.size()) {
                    index = 0;
                }
                try {
//                CrmCustomerDO customer = crmCustomerDO;
                    Long ownerUserId = ownerUserIdList.get(index);
                    // 2.1. 设置负责人
                    updateClues.add(new CrmClueDO().setId(clue.getId())
                            .setOwnerUserId(ownerUserId)
                            .setOwnerTime(LocalDateTime.now())
                            .setPoolId(null)
                            .setIntoPoolTime(null)
                            // 设置数据类型是公海领取还是分配
                            .setDataType(isReceive ? CrmDataTypeEnum.RECEIVE.getType() : CrmDataTypeEnum.DISTRIBUTE.getType())
                    );
                    // 2.2. 创建负责人数据权限
                    createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CLUE.getType())
                            .setBizId(clue.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
                    // 3. 记录操作日志
                    AdminUserRespDTO user = null;
                    if (!isReceive) {
                        user = adminUserApi.getUser(ownerUserId);
                    }
                    getSelf().receiveCustomerLog(clue, user == null ? null : user.getNickname());

                    // 添加公海操作日志
                    crmPoolActionLogService.createActionLog(CrmBizTypeEnum.CRM_CLUE.getType(), clue.getId(), CrmPoolActionTypeEnum.GET.getType());
                } catch (Exception e) {
                    // continue
                } finally {
                    index++;
                }
            }
            // 4.1 更新客户负责人
            clueMapper.updateBatch(updateClues);
            // 4.2 创建负责人数据权限
            crmPermissionService.createPermissionBatch(createPermissions);

            totalProcessed += clues.size();
            pageNum++;
            // 如果当前批次数据不足 pageSize，说明已无更多数据
            if (clues.size() < pageSize) {
                break;
            }
        }
    }

    @Override
    public CrmClueImportRespVO importClueList(List<CrmClueImportExcelVO> importClues,
                                              CrmClueImportReqVO importReqVO) {
        // 逐条处理
        CrmClueImportRespVO respVO = CrmClueImportRespVO.builder().createClueNames(new ArrayList<>())
                .updateClueNames(new ArrayList<>()).failureClueNames(new LinkedHashMap<>()).build();

        for (CrmClueImportExcelVO importCustomer : importClues) {
            try {
                if (StrUtil.isEmptyIfStr(importCustomer.getName())) {
                    importCustomer.setName("未命名");
                }
            } catch (ServiceException ex) {
                respVO.getFailureClueNames().put(importCustomer.getName(), ex.getMessage());
                continue;
            }
            // 情况一：判断如果不存在，在进行插入
            CrmClueDO existClue = null;

            if (ObjUtil.equal(importReqVO.getRepeatHandleField(), CrmImportRepeatFieldEnum.NAME.getType())) {
                existClue = clueMapper.selectByClueName(importCustomer.getName(), importReqVO.getModuleId());
            } else if (ObjUtil.equal(importReqVO.getRepeatHandleField(), CrmImportRepeatFieldEnum.MOBILE.getType())) {
                if (StrUtil.isNotEmpty(importCustomer.getMobile())) {
                    existClue = clueMapper.selectByClueMobile(importCustomer.getMobile(), importReqVO.getModuleId());
                }
            }

            // 如果没有查询到(NULL) 则直接插入新的
            if (existClue == null) {
                // 1.1 插入客户信息
                String name = createClueByImport(importCustomer, importReqVO);
                respVO.getCreateClueNames().add(name);
                continue;
            }
            // 如果已存在重复数据，那么根据处理方式，执行插入、更新、忽略
            if (ObjUtil.equal(importReqVO.getRepeatHandleType(), CrmImportRepeatTypeEnum.INSERT.getType())) {
                String name = createClueByImport(importCustomer, importReqVO);
                respVO.getCreateClueNames().add(name);
            } else if (ObjUtil.equal(importReqVO.getRepeatHandleType(), CrmImportRepeatTypeEnum.UPDATE.getType())) {
                // 2.1 更新客户信息
                CrmClueDO updateClue = BeanUtils.toBean(importCustomer, CrmClueDO.class)
                        .setId(existClue.getId());
                appendLocation(updateClue);
                clueMapper.updateById(updateClue);
                respVO.getUpdateClueNames().add(importCustomer.getName());
//                getSelf().importClueLog(updateClue, true);
            } else {
                respVO.getFailureClueNames().put(importCustomer.getName(),
                        StrUtil.format(CLUE_NAME_EXISTS_AND_IGNORE.getMsg(), importCustomer.getName()));
            }
        }
        return respVO;
    }

    public CrmClueImportRespVO customImportClueList(List<CrmClueImportExcelVO> importClues,
                                                    CrmClueImportReqVO importReqVO,Long runnableTaskId) {
        ImportTaskSaveReqVO save = new ImportTaskSaveReqVO();
        save.setId(runnableTaskId);
        save.setStatus(1);
        save.setDataCount(0);
        save.setUpdateCount(0);
        save.setInsertCount(0);
        redisTemplate.opsForValue().set("import_task:" + runnableTaskId, save);
        LeadsUploadService leadsUploadService = new LeadsUploadService();
        List<DictDataRespDTO> dictDataList = dictDataApi.getDictDataList(DictTypeConstants.CRM_CUSTOMER_SOURCE);
// 1. 校验用户是否存在
        adminUserApi.validateUserId(importReqVO.getOwnerUserId());

        leadsUploadService.importExcel(importReqVO, dictDataList,runnableTaskId);
        List<List<Object>> errorList = leadsUploadService.getErrorList();
        CrmClueImportRespVO respVO = leadsUploadService.getRespVO();

        if (errorList.size() > 1) {
            File file = new File(uploadPath + "/excel/" + BaseUtil.getDate() + "/" + IdUtil.simpleUUID() + ".xlsx");
            BigExcelWriter writer = ExcelUtil.getBigWriter(file);
            // 取消数据的黑色边框以及数据左对齐
            CellStyle cellStyle = writer.getCellStyle();
            cellStyle.setFillBackgroundColor(IndexedColors.BLACK.getIndex());
            cellStyle.setBorderTop(BorderStyle.NONE);
            cellStyle.setBorderBottom(BorderStyle.NONE);
            cellStyle.setBorderLeft(BorderStyle.NONE);
            cellStyle.setBorderRight(BorderStyle.NONE);
            cellStyle.setAlignment(HorizontalAlignment.LEFT);
            Font defaultFont = writer.createFont();
            defaultFont.setFontHeightInPoints((short) 11);
            cellStyle.setFont(defaultFont);
            // 取消数字格式的数据的黑色边框以及数据左对齐
            CellStyle cellStyleForNumber = writer.getStyleSet().getCellStyleForNumber();
            cellStyleForNumber.setBorderTop(BorderStyle.NONE);
            cellStyleForNumber.setBorderBottom(BorderStyle.NONE);
            cellStyleForNumber.setBorderLeft(BorderStyle.NONE);
            cellStyleForNumber.setBorderRight(BorderStyle.NONE);
            cellStyleForNumber.setAlignment(HorizontalAlignment.LEFT);
            cellStyleForNumber.setFillBackgroundColor(IndexedColors.BLACK.getIndex());
            cellStyleForNumber.setFont(defaultFont);

            CellStyle textStyle = writer.getWorkbook().createCellStyle();
            DataFormat format = writer.getWorkbook().createDataFormat();
            textStyle.setDataFormat(format.getFormat("@"));

            writer.merge(errorList.get(1).size() + 1, errorList.get(0).get(0).toString().trim(), true);
            writer.getHeadCellStyle().setAlignment(HorizontalAlignment.LEFT);
            writer.getHeadCellStyle().setWrapText(true);
            Font headFont = writer.createFont();
            headFont.setFontHeightInPoints((short) 11);
            writer.getHeadCellStyle().setFont(headFont);
            writer.getHeadCellStyle().setFillPattern(FillPatternType.NO_FILL);
            writer.getOrCreateRow(0).setHeightInPoints(120);
            writer.setRowHeight(-1, 20);
            for (int i = 0; i < errorList.get(1).size(); i++) {
                writer.getSheet().setDefaultColumnStyle(i, textStyle);
            }
            errorList.remove(0);
            // 一次性写出内容，使用默认样式
            writer.write(errorList);
            // 关闭writer，释放内存
            writer.close();
            BufferedInputStream inputStream = FileUtil.getInputStream(file);
            String file1 = fileApi.createFile(file.getName(), null, IoUtil.readBytes(inputStream));
            System.out.println("file1" + file1);
            respVO.setErrorFileUrl(file1);
        }
        return respVO;

    }

    public abstract class UploadService {

        @Getter
        protected List<List<Object>> errorList = new ArrayList<>();

        protected List<CrmModelFiledVO> fieldList = new ArrayList<>();

        protected List<CrmModelFiledVO> fixedFieldList = new ArrayList<>();
        protected List<CrmModelFiledVO> customFieldList = new ArrayList<>();

        protected List<CrmModelFiledVO> uniqueList = new ArrayList<>();

        @Getter
        protected CrmClueImportRespVO respVO = CrmClueImportRespVO.builder().createClueNames(new ArrayList<>())
                .updateClueNames(new ArrayList<>()).failureClueNames(new LinkedHashMap<>()).build();
        @Getter
        protected List<CustomCrmClueImportExcelVO> clueImportExcelVOS = new ArrayList<>();
        /**
         * 导入数量
         */
        @Getter
        protected Integer num = -2;

        protected List<Integer> isNullList = new ArrayList<>();

        protected boolean templateErr = false;

        protected JSONObject kv = new JSONObject();

        public abstract void importExcel(CrmClueImportReqVO importReqVO, List<DictDataRespDTO> dictDataList,Long runnableTaskId);

        protected void queryExcelHead(List<Object> rowList, Long moduleId) {
            List<CrmModelFiledVO> crmModelFiledList = new ArrayList<>();
            crmModelFiledList.add(0, new CrmModelFiledVO("name", FieldEnum.TEXT, "线索名称", 1).setIsNull(1));
            crmModelFiledList.add(1, new CrmModelFiledVO("source", FieldEnum.SELECT, "线索来源", 1).setIsNull(0));
            crmModelFiledList.add(2, new CrmModelFiledVO("mobile", FieldEnum.TEXT, "手机", 1).setIsNull(0));
            crmModelFiledList.add(3, new CrmModelFiledVO("email", FieldEnum.TEXT, "邮箱", 1).setIsNull(0));
            CustomFieldHeadPageReqVO pageReqVO = new CustomFieldHeadPageReqVO();
            pageReqVO.setModuleId(moduleId);
            pageReqVO.setTableType("1");
            List<CustomFieldHeadDO> customFieldHeadList = customFieldHeadService.getCustomFieldHeadList(pageReqVO);
            if (CollUtil.isNotEmpty(customFieldHeadList)) {
                for (CustomFieldHeadDO customFieldHeadDO : customFieldHeadList) {
                    crmModelFiledList.add(new CrmModelFiledVO(customFieldHeadDO.getFieldName(), FieldEnum.TEXT, customFieldHeadDO.getName(), 0, customFieldHeadDO.getId()).setIsNull(customFieldHeadDO.getIsRequired()));
                }
            }
            crmModelFiledList.add(new CrmModelFiledVO("remark", FieldEnum.TEXT, "备注", 1).setIsNull(0));
            crmModelFiledList.add(new CrmModelFiledVO("mapAddress", FieldEnum.MAP_ADDRESS, "地区定位", 1).setIsNull(0));
            fieldList = crmModelFiledList;
            fieldList.removeIf(record -> ExcelParseUtil.removeFieldByType(record.getType()));
            HashMap<String, String> nameMap = new HashMap<>();
            HashMap<String, Integer> isNullMap = new HashMap<>();

            fieldList.forEach(filed -> {
                if (Objects.equals(1, filed.getFieldType())) {
                    fixedFieldList.add(filed);
                } else {
                    customFieldList.add(filed);
                }
                if ("mapAddress".equals(filed.getFieldName())) {
                    nameMap.put("省", "province");
                    nameMap.put("市", "city");
                    nameMap.put("区", "site");
                    nameMap.put("详细地址", "detailAddress");
                    isNullMap.put("省", 0);
                    isNullMap.put("市", 0);
                    isNullMap.put("区", 0);
                    isNullMap.put("详细地址", 0);
                } else {
                    boolean isNull = Objects.equals(1, filed.getIsNull());
                    nameMap.put((isNull ? "*" : "") + filed.getName(), filed.getFieldName());
                    if (filed.getFieldName().equals("name")) {
                        isNullMap.put((isNull ? "*" : "") + filed.getName(), 0);
                    } else {
                        isNullMap.put((isNull ? "*" : "") + filed.getName(), filed.getIsNull());
                    }
                }
            });
            List<String> nameList = new ArrayList<>(nameMap.keySet());
            if (nameList.size() != rowList.size() || !nameList.containsAll(rowList)) {
                templateErr = true;
            } else {
                for (int i = 0; i < rowList.size(); i++) {
                    kv.put(nameMap.get(rowList.get(i).toString()), i);
                    if (Objects.equals(1, isNullMap.get(rowList.get(i).toString()))) {
                        isNullList.add(i);
                    }
                }
            }
            rowList.add(0, "错误原因");
            errorList.add(rowList);
        }

    }

    public class LeadsUploadService extends UploadService {
        Area area = AreaUtils.getArea(Area.ID_CHINA);
        List<Area> children = area.getChildren();
        @Override
        public void importExcel(CrmClueImportReqVO importReqVO, List<DictDataRespDTO> dictDataList,Long runnableTaskId) {
            long start = System.currentTimeMillis();
            Map<String, List<String>> areaMap = CrmExcelUtil.getAreaMap();
            // 逐条处理
            // 创建 RowHandler
            ExcelReader reader = new ExcelReader(FileUtil.file(importReqVO.getFilepath()),0);
            List<CrmClueDO>insertCrmClueDOList = Collections.synchronizedList(new ArrayList<>());;
            List<CrmClueDO>updateCrmClueDOList = Collections.synchronizedList(new ArrayList<>());;
            List<CustomFieldDataDO> addCustomFieldDataDOList = Collections.synchronizedList(new ArrayList<>());;
            List<CrmPermissionCreateReqBO> addCrmPermissionList = Collections.synchronizedList(new ArrayList<>());;
            List<CustomFieldDataDO> updateCustomFieldDataDOList =Collections.synchronizedList(new ArrayList<>());;
            List<Long> delDataIdList =Collections.synchronizedList(new ArrayList<>());;
            List<List<Object>> readAll = reader.read();
            List<Object> rowList = readAll.get(1);
            queryExcelHead(rowList, importReqVO.getModuleId());
            List<List<Object>> errorRows = Collections.synchronizedList(new ArrayList<>());
            // 将数据分批处理，使用多线程提高导入效率
            final List<List<Object>> dataList = readAll.subList(2, readAll.size());
            // 校验是否有必填字段为空
            for (List<Object> currentRow : dataList) {
                if (currentRow.size() < kv.entrySet().size()) {
                    for (int j = currentRow.size() - 1; j < kv.entrySet().size(); j++) {
                        currentRow.add(null);
                    }
                }
                for (Integer integer : isNullList) {
                    if (ObjectUtil.isEmpty(currentRow.get(integer))) {
                        errorRows.add(Arrays.asList("必填字段未填写", rowList.get(integer+1)));
                        break;
                    }
                }
                if (errorRows.size() > 0) {
                    break;
                }
            }
            if (errorRows.size() > 0) {
                errorList.addAll(errorRows);
                return;
            }
            if (templateErr) {
                respVO.getFailureClueNames().put("请使用最新的模板",
                        StrUtil.format(CLUE_IS_NOT_NULL.getMsg(), "请使用最新的模板"));
                rowList.add(0, "请使用最新的模板");
                errorList.add(rowList);
            }else {
                final int batchSize = 500; // 每批处理的数据量
                // 创建同步集合存储结果
                List<String> createClueNames = Collections.synchronizedList(new ArrayList<>());
                List<String> updateClueNames = Collections.synchronizedList(new ArrayList<>());
                Map<String, String> failureClueNames = Collections.synchronizedMap(new LinkedHashMap<>());
                List<CustomCrmClueImportExcelVO> clueImportExcelVOList = Collections.synchronizedList(new ArrayList<>());
                AtomicInteger dataCount = new AtomicInteger(0);
                AtomicInteger addCount = new AtomicInteger(0);
                AtomicInteger updateCount = new AtomicInteger(0);

                // 使用并行流处理数据批次
                List<List<List<Object>>> batches = new ArrayList<>();
                for (int i = 0; i < dataList.size(); i += batchSize) {
                    int endIndex = Math.min(i + batchSize, dataList.size());
                    batches.add(dataList.subList(i, endIndex));
                }
                // 并行处理所有批次
                int threadCount = 60; // 指定线程数
                System.out.println("threadCount" + threadCount);
                ForkJoinPool customThreadPool = new ForkJoinPool(threadCount);
                try{
                    Long zx = System.currentTimeMillis();
                    customThreadPool.submit(() ->
                            batches.parallelStream().forEach(batch -> {
                                processBatch(batch, importReqVO, dictDataList, areaMap, children,
                                        kv, isNullList, fixedFieldList, customFieldList,
                                        createClueNames, updateClueNames, failureClueNames,
                                        errorRows, clueImportExcelVOList, dataCount,insertCrmClueDOList,updateCrmClueDOList, addCrmPermissionList,runnableTaskId,addCount,updateCount);
                            })
                    ).get();
                    System.out.println("执行数据时长:" + (System.currentTimeMillis() - zx));
                    ImportTaskSaveReqVO save = new ImportTaskSaveReqVO();
                    save.setId(runnableTaskId);
                    save.setStatus(2);
                    save.setDataCount(dataCount.get());
                    save.setUpdateCount(updateCount.get());
                    save.setInsertCount(addCount.get());
                    redisTemplate.opsForValue().set("import_task:" + runnableTaskId,save);
                    respVO.setDataCount(dataCount.get());
                    respVO.setUpdateCount(updateCount.get());
                    respVO.setAddCount(addCount.get());
                }catch (Exception e){
                    Thread.currentThread().interrupt(); // 恢复中断状态
                    System.err.println("任务执行被中断: " + e.getMessage());
                    e.printStackTrace();
                }finally {
                    customThreadPool.shutdown();
                }
                // 合并结果
                respVO.getCreateClueNames().addAll(createClueNames);
                respVO.getUpdateClueNames().addAll(updateClueNames);
                respVO.getFailureClueNames().putAll(failureClueNames);
                errorList.addAll(errorRows);
                System.out.println("errorList"+errorList.size());
                clueImportExcelVOS.addAll(clueImportExcelVOList);
                num = dataCount.get();
            }

            // 创建一个线程池
            AtomicInteger dataCount = new AtomicInteger(0);

            System.out.println("insertCrmClueDOList" + insertCrmClueDOList.size());
            System.out.println("updateCrmClueDOList" + updateCrmClueDOList.size());
            ForkJoinPool customThreadPool = new ForkJoinPool(50);
            try {
                customThreadPool.submit(() ->
                        insertCrmClueDOList.parallelStream().forEach(crmClueDO -> {
                            Long startTime = System.currentTimeMillis();
                            clueMapper.insert(crmClueDO);
                            System.out.println("批次 插入完成，耗时 " + (System.currentTimeMillis() - startTime) + "ms");
//                    //添加自定义
                            if (CollUtil.isNotEmpty(crmClueDO.getCustomFieldDataList())) {
                                for (CustomFieldDataDO customFieldDataDO : crmClueDO.getCustomFieldDataList()) {
                                    customFieldDataDO.setDataId(crmClueDO.getId());
                                    addCustomFieldDataDOList.add(customFieldDataDO);
                                }
                            }
//                            //权限
                            CrmPermissionCreateReqBO createPermission = new CrmPermissionCreateReqBO()
                                    .setBizType(CrmBizTypeEnum.CRM_CLUE.getType())
                                    .setBizId(crmClueDO.getId())
                                    .setUserId(crmClueDO.getOwnerUserId())
                                    .setLevel(CrmPermissionLevelEnum.OWNER.getLevel());
                            addCrmPermissionList.add(createPermission);
//                            try {
//                                Long q = System.currentTimeMillis();
//                                addCrmPermissionList.add(createPermission);
//                                System.out.println("权限执行完成时间"+(System.currentTimeMillis() - q));
//                            } catch (Exception e) {
//                                System.out.println("用户已有权限");
//                            }
//                    //修改状态
//                            ImportTaskSaveReqVO save = new ImportTaskSaveReqVO();
//                            save.setId(runnableTaskId);
//                            save.setStatus(1);
//                            save.setDataCount(dataCount.incrementAndGet());
//                            save.setUpdateCount(updateCount.get());
//                            save.setInsertCount(addCount.incrementAndGet());
//                            redisTemplate.opsForValue().set("import_task:" + runnableTaskId, save);
                        })

                ).get();
                customThreadPool.submit(() ->updateCrmClueDOList.parallelStream().forEach(crmClueDO -> {
                    Long startTime = System.currentTimeMillis();
                    clueMapper.updateById(crmClueDO);
                    System.out.println("批次修改完成，耗时 " + (System.currentTimeMillis() - startTime) + "ms");
                    if (CollUtil.isNotEmpty(crmClueDO.getCustomFieldDataList())) {
                        for (CustomFieldDataDO customFieldDataDO : crmClueDO.getCustomFieldDataList()) {
                            customFieldDataDO.setDataId(crmClueDO.getId());
                            addCustomFieldDataDOList.add(customFieldDataDO);
                            delDataIdList.add(customFieldDataDO.getId());
                        }
                    }
//                    CrmPermissionCreateReqBO createPermission = new CrmPermissionCreateReqBO()
//                            .setBizType(CrmBizTypeEnum.CRM_CLUE.getType())
//                            .setBizId(crmClueDO.getId())
//                            .setUserId(importReqVO.getOwnerUserId())
//                            .setLevel(CrmPermissionLevelEnum.OWNER.getLevel());
//                    addCrmPermissionList.add(createPermission);
//                    //修改状态
//                    ImportTaskSaveReqVO save = new ImportTaskSaveReqVO();
//                    save.setId(runnableTaskId);
//                    save.setStatus(1);
//                    save.setDataCount(dataCount.incrementAndGet());
//                    save.setUpdateCount(updateCount.incrementAndGet());
//                    save.setInsertCount(addCount.get());
//                    redisTemplate.opsForValue().set("import_task:" + runnableTaskId, save);
                })).get();
                //修改状态

                System.out.println("所有批次完成");
                System.out.println("总时长:" + (System.currentTimeMillis() - start));
                if (CollectionUtil.isNotEmpty(addCrmPermissionList)) {
                    customThreadPool.submit(() ->
                            addCrmPermissionList.parallelStream().forEach(customFieldDataDO -> {
                                Long q = System.currentTimeMillis();
                                crmPermissionService.createPermissionNew(customFieldDataDO);
                                System.out.println("权限时长"+(System.currentTimeMillis()- q));
                            })
                    ).get();
                }
                if (CollectionUtil.isNotEmpty(delDataIdList)) {
                    customThreadPool.submit(() ->
                            delDataIdList.parallelStream().forEach(dataId -> {
                                Long q = System.currentTimeMillis();
                                customFieldDataService.deleteBydataId(dataId);
                                System.out.println("删除自定义时长"+(System.currentTimeMillis()- q));
                            })
                    ).get();
                }
                if (CollectionUtil.isNotEmpty(addCustomFieldDataDOList)) {
                    customThreadPool.submit(() ->
                            addCustomFieldDataDOList.parallelStream().forEach(customFieldDataDO -> {
                                customFieldDataDO.setId(null);
                                Long q = System.currentTimeMillis();
                                customFieldDataService.insertOn(customFieldDataDO);
                                System.out.println("自定义时长"+(System.currentTimeMillis()- q));
                            })
                    ).get();
                }
            } catch (Exception e) {
                Thread.currentThread().interrupt(); // 恢复中断状态
                System.err.println("任务执行被中断: " + e.getMessage());
                e.printStackTrace();
            } finally {
                customThreadPool.shutdown();
            }
        }

        /**
         * 批量处理数据
         */
        private void processBatch(List<List<Object>> batchData, CrmClueImportReqVO importReqVO,
                                  List<DictDataRespDTO> dictDataList, Map<String, List<String>> areaMap,
                                  List<Area> children, JSONObject kv, List<Integer> isNullList,
                                  List<CrmModelFiledVO> fixedFieldList, List<CrmModelFiledVO> customFieldList,
                                  List<String> createClueNames, List<String> updateClueNames,
                                  Map<String, String> failureClueNames, List<List<Object>> errorRows,
                                  List<CustomCrmClueImportExcelVO> clueImportExcelVOList, AtomicInteger dataCount,List<CrmClueDO> insertCrmClueDOList,List<CrmClueDO> updateCrmClueDOList,List<CrmPermissionCreateReqBO> addCrmPermissionList,Long runnableTaskId,AtomicInteger addCount,AtomicInteger updateCount) {
            for (List<Object> rowList : batchData) {
//                int currentCount = dataCount.incrementAndGet();
//                // 实时打印当前处理的记录数
//                if (currentCount % 100 == 0) { // 每处理100条记录打印一次
//                    System.out.println(System.currentTimeMillis() + "已处理记录数: " + currentCount);
//                }
                List<Object> currentRow = new ArrayList<>(rowList);
                try {
                    // 设置空字段
                    if (currentRow.size() < kv.entrySet().size()) {
                        for (int j = currentRow.size() - 1; j < kv.entrySet().size(); j++) {
                            currentRow.add(null);
                        }
                    }
//                    // 校验是否有必填字段为空
//                    for (Integer integer : isNullList) {
//                        if (ObjectUtil.isEmpty(currentRow.get(integer))) {
//                            synchronized (failureClueNames) {
//                                failureClueNames.put("必填字段未填写",
//                                        StrUtil.format(CLUE_IS_NOT_NULL.getMsg(), "必填字段未填写"));
//                            }
//                            currentRow.add(0, "必填字段未填写");
//                            errorRows.add(currentRow);
//                            return;
//                        }
//                    }
                    String province = ObjectUtil.isEmpty(currentRow.get(kv.getInt("province"))) ? "" : currentRow.get(kv.getInt("province")).toString();
                    String city = ObjectUtil.isEmpty(currentRow.get(kv.getInt("city"))) ? "" : currentRow.get(kv.getInt("city")).toString();
                    String site = ObjectUtil.isEmpty(currentRow.get(kv.getInt("site"))) ? "" : currentRow.get(kv.getInt("site")).toString();
                    String detailAddress = ObjectUtil.isEmpty(currentRow.get(kv.getInt("detailAddress"))) ? "" : currentRow.get(kv.getInt("detailAddress")).toString();

                    if (StrUtil.isNotEmpty(province) && (!areaMap.containsKey(province) || !areaMap.get(province).contains(city))) {
                        currentRow.add(0, "省市区不合要求，请核对");
                        errorRows.add(currentRow);
                        return;
                    }
                    if (StrUtil.isNotEmpty(site) && (!areaMap.containsKey(city) || !areaMap.get(city).contains(site))) {
                        currentRow.add(0, "省市区不合要求，请核对");
                        errorRows.add(currentRow);
                        return;
                    }
                    JSONObject entityObject = new JSONObject();
                    JSONObject customEntityObject = new JSONObject();

                    for (CrmModelFiledVO field : fixedFieldList) {
                        String fieldName = field.getFieldName();
                        if (field.getFormType().equals(FieldEnum.SELECT.getFormType())) {
                            Object o = currentRow.get(kv.getInt(fieldName));
                            for (DictDataRespDTO dictDataRespDTO : dictDataList) {
                                if (Objects.equals(dictDataRespDTO.getLabel(), o)) {
                                    entityObject.put(fieldName, dictDataRespDTO.getValue());
                                    break;
                                }
                            }
                        } else {
                            if (!"mapAddress".equals(fieldName)) {
                                entityObject.put(fieldName, currentRow.get(kv.getInt(fieldName)));
                            }
                        }
                    }
                    Area provinceRet = CollUtil.findOne(children, area -> province.equals(area.getName()));
                    if (provinceRet != null) {
                        Area cityRet = CollUtil.findOne(provinceRet.getChildren(), area -> city.equals(area.getName()));
                        if (cityRet != null) {
                            Area siteRet = CollUtil.findOne(cityRet.getChildren(), area -> site.equals(area.getName()));
                            if (siteRet != null) {
                                entityObject.put("areaId", siteRet.getId());
                            }
                        }
                    }
                    if (StrUtil.isNotEmpty(detailAddress)) {
                        entityObject.put("detailAddress", detailAddress);
                    }
                    // 组装自定义字段
                    List<CustomFieldDataDO> customFieldDataDOList = new ArrayList<>();
                    for (CrmModelFiledVO field : customFieldList) {
                        String fieldName = field.getFieldName();
                        customEntityObject.put(fieldName, currentRow.get(kv.getInt(fieldName)));
                        CustomFieldDataDO customFieldDataDO = new CustomFieldDataDO();
                        customFieldDataDO.setFieldName(fieldName);
                        customFieldDataDO.setFieldHeadId(field.getFieldHeadId());
                        customFieldDataDO.setFieldDate(currentRow.get(kv.getInt(fieldName)) == null ? "" : currentRow.get(kv.getInt(fieldName)).toString());
                        customFieldDataDOList.add(customFieldDataDO);
                    }

                    CustomCrmClueImportExcelVO importCustomer = new CustomCrmClueImportExcelVO();
                    BeanUtil.copyProperties(entityObject, importCustomer);
                    if (StringUtils.isEmpty(importCustomer.getName())) {
                        importCustomer.setName("未命名");
                    }
                    importCustomer.setCustomFieldDataDOList(customFieldDataDOList);
                    // 情况一：判断如果不存在，在进行插入
                    CrmClueDO existClue = null;
                    if (ObjUtil.equal(importReqVO.getRepeatHandleField(), CrmImportRepeatFieldEnum.NAME.getType())) {
                        existClue = clueMapper.selectByNameAndModuleId(importCustomer.getName(), importReqVO.getModuleId());
                    } else if (ObjUtil.equal(importReqVO.getRepeatHandleField(), CrmImportRepeatFieldEnum.MOBILE.getType())) {
                        if (StrUtil.isNotEmpty(importCustomer.getMobile())) {
                            existClue = clueMapper.selectByMobileAndModuleId(importCustomer.getMobile(), importReqVO.getModuleId());
                        }
                    }
                    // 插入逻辑
                    if (existClue == null) {
                        // 1.1 插入客户信息
                        CrmClueDO clueInsert = createCustomClueByImportNew(importCustomer, importReqVO);

                        clueInsert.setCustomFieldDataList(importCustomer.getCustomFieldDataDOList());
                        insertCrmClueDOList.add(clueInsert);
                        ImportTaskSaveReqVO save = new ImportTaskSaveReqVO();
                        save.setId(runnableTaskId);
                        save.setStatus(1);
                        save.setDataCount(dataCount.incrementAndGet());
                        save.setUpdateCount(updateCount.get());
                        save.setInsertCount(addCount.incrementAndGet());
                        redisTemplate.opsForValue().set("import_task:" + runnableTaskId, save);
//                        synchronized (createClueNames) {
//                            createClueNames.add(clueInsert.getName());
//                        }
                    } else {
                        // 如果已存在重复数据，那么根据处理方式，执行插入、更新、忽略
                        if (ObjUtil.equal(importReqVO.getRepeatHandleType(), CrmImportRepeatTypeEnum.INSERT.getType())) {
                            CrmClueDO clueInsert = createCustomClueByImportNew(importCustomer, importReqVO);
                            clueInsert.setCustomFieldDataList(importCustomer.getCustomFieldDataDOList());
                            insertCrmClueDOList.add(clueInsert);
                            ImportTaskSaveReqVO save = new ImportTaskSaveReqVO();
                            save.setId(runnableTaskId);
                            save.setStatus(1);
                            save.setDataCount(dataCount.incrementAndGet());
                            save.setUpdateCount(updateCount.get());
                            save.setInsertCount(addCount.incrementAndGet());
                            redisTemplate.opsForValue().set("import_task:" + runnableTaskId, save);
//                            synchronized (createClueNames) {
//                               createClueNames.add(clueInsert.getName());
//                            }
                        } else if (ObjUtil.equal(importReqVO.getRepeatHandleType(), CrmImportRepeatTypeEnum.UPDATE.getType())) {
//                            // 2.1 更新客户信息
                            CrmClueDO updateClue = BeanUtils.toBean(importCustomer, CrmClueDO.class)
                                    .setId(existClue.getId());
                            updateCrmClueDOList.add(updateClue);
                            CrmPermissionCreateReqBO crmPermissionCreateReqBO = new CrmPermissionCreateReqBO()
                                    .setBizType(CrmBizTypeEnum.CRM_CLUE.getType())
                                    .setBizId(updateClue.getId())
                                    .setUserId(importReqVO.getOwnerUserId())
                                    .setLevel(CrmPermissionLevelEnum.OWNER.getLevel());
                            Long a = System.currentTimeMillis();
                            Long countPermission = crmPermissionService.validatePermissionNotExistsNew(crmPermissionCreateReqBO);
                            if (countPermission <=0) {
                                addCrmPermissionList.add(crmPermissionCreateReqBO);
                            }
////                            clueMapper.updateById(updateClue);
                            if (CollUtil.isNotEmpty(importCustomer.getCustomFieldDataDOList())) {
                                List<CustomFieldDataDO> customFieldDataList = importCustomer.getCustomFieldDataDOList();
                                for (CustomFieldDataDO customFieldDataDO : customFieldDataList) {
                                    customFieldDataDO.setDataId(Long.valueOf(updateClue.getId()));
                                }
                                updateClue.setCustomFieldDataList(customFieldDataList);
                            }
                            ImportTaskSaveReqVO save = new ImportTaskSaveReqVO();
                            save.setId(runnableTaskId);
                            save.setStatus(1);
                            save.setDataCount(dataCount.incrementAndGet());
                            save.setUpdateCount(updateCount.incrementAndGet());
                            save.setInsertCount(addCount.get());
                            redisTemplate.opsForValue().set("import_task:" + runnableTaskId, save);
//                            synchronized (updateClueNames) {
//                                updateClueNames.add(importCustomer.getName());
//                            }
                        } else {
//                            synchronized (failureClueNames) {
//                                failureClueNames.put(importCustomer.getName(),
//                                        StrUtil.format(CLUE_NAME_EXISTS_AND_IGNORE.getMsg(), importCustomer.getName()));
//                            }
                            currentRow.add(0, "重复操作跳过此数据");
                            errorRows.add(currentRow);
                        }
                        synchronized (clueImportExcelVOList) {
                            clueImportExcelVOList.add(importCustomer);
                        }
//                        System.out.println("自定义" + customEntityObject.toString());
                    }
                } catch (Exception e) {
                    log.error("处理行数据时发生异常", e);
                    synchronized (failureClueNames) {
                        failureClueNames.put("线索导入异常", StrUtil.format("线索导入异常", ""));
                    }
                    currentRow.add(0, "导入异常");
                    errorRows.add(currentRow);
                }
            }

        }
    }

    public String createClueByImport(CrmClueImportExcelVO importCustomer, CrmClueImportReqVO importReqVO) {
        CrmClueDO customer = initClue(importCustomer, ObjectUtil.equal(importReqVO.getPool(), true) ? null : importReqVO.getOwnerUserId());
        customer.setModuleId(importReqVO.getModuleId());
        appendLocation(customer);
        clueMapper.insert(customer);
        // 1.2 创建数据权限
        if (customer.getOwnerUserId() != null) {
            crmPermissionService.createPermission(
                    new CrmPermissionCreateReqBO()
                            .setBizType(CrmBizTypeEnum.CRM_CLUE.getType())
                            .setBizId(customer.getId())
                            .setUserId(customer.getOwnerUserId())
                            .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
        }
        // 1.3 记录操作日志
////        getSelf().importClueLog(customer, false);
//        List<CustomFieldDataDO> customFieldDataDOList = importCustomer.getCustomFieldDataDOList();
////        List<CustomFieldDataDO> customFieldDataDOList =new ArrayList<>();
//        if (CollUtil.isNotEmpty(customFieldDataDOList)) {
//            for (CustomFieldDataDO customFieldDataDO : customFieldDataDOList) {
//                customFieldDataDO.setDataId(customer.getId());
//            }
//            customFieldDataMapper.insertBatch(customFieldDataDOList);
//        }
        return importCustomer.getName();
    }

    public String createCustomClueByImport(CustomCrmClueImportExcelVO importCustomer, CrmClueImportReqVO importReqVO) {
        CrmClueDO customer = initClue(importCustomer, ObjectUtil.equal(importReqVO.getPool(), true) ? null : importReqVO.getOwnerUserId());
        customer.setModuleId(importReqVO.getModuleId());
        customer.setTenantId(importReqVO.getTenantId());
        appendLocation(customer);
        Long i= System.currentTimeMillis();
        clueMapper.insert(customer);
        System.out.println("插入时间" + (System.currentTimeMillis()-i));
        // 1.2 创建数据权限
        if (customer.getOwnerUserId() != null) {
            Long c = System.currentTimeMillis();
            crmPermissionService.createPermissionNew(
                    new CrmPermissionCreateReqBO()
                            .setBizType(CrmBizTypeEnum.CRM_CLUE.getType())
                            .setBizId(customer.getId())
                            .setUserId(customer.getOwnerUserId())
                            .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
            System.out.println("权限" + (System.currentTimeMillis()-c));
        }
//        // 1.3 记录操作日志
////        getSelf().importClueLog(customer, false);
        List<CustomFieldDataDO> customFieldDataDOList = importCustomer.getCustomFieldDataDOList();
        if (CollUtil.isNotEmpty(customFieldDataDOList)) {
            for (CustomFieldDataDO customFieldDataDO : customFieldDataDOList) {
                customFieldDataDO.setDataId(customer.getId());
            }
            customFieldDataService.insertList(customFieldDataDOList);
        }
        return importCustomer.getName();
    }

    public CrmClueDO createCustomClueByImportNew(CustomCrmClueImportExcelVO importCustomer, CrmClueImportReqVO importReqVO) {
        CrmClueDO customer = initClue(importCustomer, ObjectUtil.equal(importReqVO.getPool(), true) ? null : importReqVO.getOwnerUserId());
        customer.setModuleId(importReqVO.getModuleId());
        customer.setTenantId(importReqVO.getTenantId());
        appendLocation(customer);
        return customer;
//        Long i= System.currentTimeMillis();
//        clueMapper.insertCrmClue(customer);
//        System.out.println("插入时间" + (System.currentTimeMillis()-i));
//        // 1.2 创建数据权限
//        if (customer.getOwnerUserId() != null) {
//            Long c = System.currentTimeMillis();
//            crmPermissionService.createPermissionNew(
//                    new CrmPermissionCreateReqBO()
//                            .setBizType(CrmBizTypeEnum.CRM_CLUE.getType())
//                            .setBizId(customer.getId())
//                            .setUserId(customer.getOwnerUserId())
//                            .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
//            System.out.println("权限" + (System.currentTimeMillis()-c));
//        }
////        // 1.3 记录操作日志
//////        getSelf().importClueLog(customer, false);
//        List<CustomFieldDataDO> customFieldDataDOList = importCustomer.getCustomFieldDataDOList();
//        if (CollUtil.isNotEmpty(customFieldDataDOList)) {
//            for (CustomFieldDataDO customFieldDataDO : customFieldDataDOList) {
//                customFieldDataDO.setDataId(customer.getId());
//            }
//            customFieldDataService.insertList(customFieldDataDOList);
//        }
//        return importCustomer.getName();
    }

    @Override
    public Map<Long, List<CrmLabelDO>> getClueLabelMap(List<Long> clueIds) {
        List<CrmClueLabelDO> clueLabelList = crmClueLabelMapper.selectList(new LambdaQueryWrapperX<CrmClueLabelDO>().in(CrmClueLabelDO::getClueId, clueIds));

        if (CollUtil.isEmpty(clueLabelList)) {
            return new HashMap<>();
        }
        // 获取标签对象
        List<CrmLabelDO> labelList = crmLabelMapper.selectBatchIds(clueLabelList.stream().map(CrmClueLabelDO::getLabelId).collect(Collectors.toList()));
        if (CollUtil.isEmpty(labelList)) {
            return new HashMap<>();
        }
        Map<Long, List<CrmLabelDO>> result = new HashMap<>();
        clueIds.forEach(clueId -> {
            List<CrmClueLabelDO> tempCustomerLabelList = clueLabelList.stream().filter(data -> ObjectUtil.equal(data.getClueId(), clueId)).collect(Collectors.toList());
            List<Long> labelIds = tempCustomerLabelList.stream().map(CrmClueLabelDO::getLabelId).collect(Collectors.toList());
            List<CrmLabelDO> tempLabelList = labelList.stream().filter(data -> labelIds.contains(data.getId())).collect(Collectors.toList());
            CollectionUtil.sort(tempLabelList, (x, y) -> NumberUtil.compare(x.getSort(), y.getSort()));
            result.put(clueId, tempLabelList);
        });

        // 按照sort排序
        return result;
    }

    @Override
    public Boolean updateClueLabel(CrmClueLabelUpdateReqVO reqVO) {
        crmClueLabelMapper.delete(CrmClueLabelDO::getClueId, reqVO.getClueId());

        if (CollUtil.isEmpty(reqVO.getLabelList())) {
            return true;
        }

        List<Long> labelIds = reqVO.getLabelList();
        labelIds.forEach(labelId -> {
            crmClueLabelMapper.insert(new CrmClueLabelDO().setClueId(reqVO.getClueId()).setLabelId(labelId));
        });

        return true;
    }

    @Override
    public Boolean batchUpdateClueLabel(CrmClueLabelUpdateReqVO reqVO) {
        if (CollUtil.isEmpty(reqVO.getClueIdList())) {
            return true;
        }

        List<Long> clueIdList = reqVO.getClueIdList();
        clueIdList.forEach(
                clueId -> {
                    // 查询： 已有该标签，跳过； 否则插入
                    reqVO.getLabelList().forEach(labelId -> {
                        Long count = crmClueLabelMapper.selectCount(new LambdaQueryWrapperX<CrmClueLabelDO>().eq(CrmClueLabelDO::getClueId, clueId).eq(CrmClueLabelDO::getLabelId, labelId));
                        if (count == 0) {
                            crmClueLabelMapper.insert(new CrmClueLabelDO().setClueId(clueId).setLabelId(labelId));
                        }
                    });
                }
        );
        return true;
    }

    @Override
    public PageResult<CrmClueDO> getPutPoolRemindCluePage(CrmCluePageReqVO pageVO, Long loginUserId) {
        CrmCluePoolConfigDO poolConfig = cluePoolConfigService.getCluePoolConfigByModule(pageVO.getModuleId());
        if (ObjUtil.isNull(poolConfig)
                || Boolean.FALSE.equals(poolConfig.getEnabled())
                || Boolean.FALSE.equals(poolConfig.getNotifyEnabled())) {
            return PageResult.empty();
        }
//        List<CrmCustomerPoolConfigDO> customerPoolList = customerPoolConfigService.getCustomerPoolList();
//        if (CollUtil.isEmpty(customerPoolList)) {
//            return PageResult.empty();
//        }
        return clueMapper.selectPutPoolRemindCluePage(pageVO, poolConfig, loginUserId);

    }

    @Override
    public Map<Long, Long> getPutPoolRemindClueCount(Long userId) {
        List<CrmModuleDO> enableModuleList = moduleService.getEnableModuleList(userId);

        List<CrmCluePoolConfigDO> poolList = cluePoolConfigService.getCluePoolListByModuleIds(convertList(enableModuleList, CrmModuleDO::getId));
        if (CollUtil.isEmpty(poolList)) {
            return MapUtil.empty();
        }

        Map<Long, Long> result = new HashMap<>();
        for (CrmCluePoolConfigDO poolConfig : poolList) {
            CrmCluePageReqVO pageVO = new CrmCluePageReqVO()
                    .setPool(null)
//                    .setContactStatus(CrmCustomerPageReqVO.CONTACT_TODAY)
                    .setSceneType(CrmSceneTypeEnum.OWNER.getType());
            result.put(poolConfig.getModuleId(), clueMapper.selectPutPoolRemindClueCount(pageVO, poolConfig, userId));
        }

        return result;
    }

    @Override
    public Long getTodayContactClueCount(Long loginUserId) {
        return clueMapper.getTodayContactClueCount(loginUserId);
    }

    @Override
    public Long getOverdueContactClueCount(Long loginUserId) {
        return clueMapper.getOverdueContactClueCount(loginUserId);
    }

    @Override
    public Boolean updateClueStage(CrmClueStageUpdateReqVO reqVO) {
        clueMapper.update(new LambdaUpdateWrapper<CrmClueDO>().eq(CrmClueDO::getId, reqVO.getId()).set(CrmClueDO::getStageId, reqVO.getStageId()));
        return true;
    }

    @Override
    public int autoPutCluePool() {
        AtomicInteger count = new AtomicInteger();

        List<CrmCluePoolConfigDO> poolList = cluePoolConfigService.getCluePoolList();
        if (CollUtil.isEmpty(poolList)) {
            return count.get();
        }
        poolList.forEach(pool -> {
            List<CrmClueDO> clueList = clueMapper.selectListByAutoPool(pool);
            for (CrmClueDO customer : clueList) {
                try {
                    getSelf().putCluePool(customer);
                    count.getAndIncrement();
                } catch (Throwable e) {
                    log.error("[autoPutCluePool][线索({}) 放入公海异常]", customer.getId(), e);
                }
            }
        });

        return count.get();
    }

    /**
     * 记录导入线索时的操作日志
     *
     * @param clue 线索信息
     */
    @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_IMPORT_SUB_TYPE, bizNo = "{{#clue.id}}",
            success = CRM_CLUE_IMPORT_SUCCESS)
    public void importClueLog(CrmClueDO clue, boolean isUpdate) {
        LogRecordContext.putVariable("clue", clue);
        LogRecordContext.putVariable("isUpdate", isUpdate);
    }

    private static CrmClueDO initClue(Object clue, Long userId) {
        return BeanUtils.toBean(clue, CrmClueDO.class)
                .setOwnerUserId(userId)
//                .setPoolId(poolId)
                .setIntoPoolTime(LocalDateTime.now())
                .setOwnerTime(LocalDateTime.now());
    }

    @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_RECEIVE_SUB_TYPE, bizNo = "{{#clue.id}}",
            success = CRM_CLUE_RECEIVE_SUCCESS)
    public void receiveCustomerLog(CrmClueDO clue, String ownerUserName) {
        // 记录操作日志上下文
        LogRecordContext.putVariable("clue", clue);
        LogRecordContext.putVariable("ownerUserName", ownerUserName);
    }

    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private CrmClueServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

    /**
     * CrmClueDO增加手机号归属地
     */
    private void appendLocation(CrmClueDO clue) {
        if (StrUtil.isNotEmpty(clue.getMobile())) {
            NumberLocationDTO location = locationApi.selectByNumber(clue.getMobile());
            if(Objects.nonNull(location)){
                clue.setCity(location.getCity());
                clue.setProvinces(location.getProvinces());
                clue.setFacilitator(location.getFacilitator());
            }
        }
    }
}
